package com.allwees.core.common.util.reflect;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <ol>Java对象转换工具类，主要包括以下常用功能：
 * <li>objToMap - 对象转Map</li>
 * <li>mapToObj - Map转对象</li>
 * <li>copyField - 对象属性深度复制（按指定字段）</li>
 * <li>copyFieldWithOmit - 对象属性深度复制（忽略指定字段）</li>
 * <li>isSimpleType - 判断是否是简单对象</li>
 * </ol>
 *
 * @author yangylsky
 */
public class PojoUtil {
	private static final Logger logger = LoggerFactory.getLogger(PojoUtil.class);

	/**
	 * @param map
	 * @param obj
	 */
	public static void mapToObj(Map<?, ?> map, Object obj) {
		if(logger.isDebugEnabled()) {
			logger.debug(map.toString());
		}
        for (Object o : map.keySet()) {
            String name = (String) o;
            if (PropertyUtils.isWriteable(obj, name)) {
                Object value = map.get(name);
                try {
                    BeanUtils.copyProperty(obj, name, value);
                } catch (Exception e) {
                    if (logger.isDebugEnabled()) {
                        logger.error("", e);
                    }
                }
            }
        }
	}

	/**
	 * @param obj
	 * @param map
	 */
	public static void objToMap(Object obj, Map<String, Object> map) {
		PojoUtil.objToMap(obj, map, false);
	}

	/**
	 * @param obj
	 * @param map
	 * @param nullToBlank
	 */
	public static void objToMap(Object obj, Map<String, Object> map, boolean nullToBlank) {
		if(map == null) {
			map = new HashMap<>();
		}
		PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(obj);
		for(PropertyDescriptor descriptor : descriptors) {
			String name = descriptor.getName();
			try {
				if(descriptor.getReadMethod() != null) {
					map.put(name, _getFieldValue(obj, name));
				}
			} catch(NullPointerException | IllegalArgumentException e) {
				if(nullToBlank) {
					map.put(name, "");
				}
			} catch(Exception e) {
				if(logger.isDebugEnabled()) {
					logger.error("", e);
				}
			}
		}
		if(logger.isDebugEnabled()) {
			logger.debug(map.toString());
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> T[] toArray(Collection<T> c, Class<T> k) {
		return c.toArray((T[]) Array.newInstance(k, 0));
	}

	/**
	 * 判断对象是否是基本对象，包括CharSequence, Number, Boolean, Date, Character
	 *
	 * @return
	 */
	public static <T> boolean isSimpleType(T o) {
		return o instanceof CharSequence || o instanceof Number || o instanceof Boolean || o instanceof Date
				|| o instanceof Character;
	}

	/**
	 * 深度复制字段到目标对象（指定字段）
	 *
	 * @param destObj - 目标对象
	 * @param srcObj  - 来源对象
	 * @param fields  - 要复制的字段，例如["name", "group.name", "group.parent.name", ...]，当fields为空时，
	 *                复制整个对象的所有字段
	 * @return
	 */
	public static <T1, T2> T1 copy(T1 destObj, T2 srcObj, String... fields) {
		return $copy(destObj, srcObj, false, fields);
	}

	/**
	 * 深度复制字段到目标对象（排除字段）
	 *
	 * @param destObj    - 目标对象
	 * @param srcObj     - 来源对象
	 * @param omitFields - 要排除的字段，例如["name", "group.name", "group.parent.name", ...]，当omitFields为空时，
	 *                   复制整个对象的所有字段
	 * @return
	 */
	public static <T1, T2> T1 copyWithOmit(T1 destObj, T2 srcObj, String... omitFields) {
		if(ArrayUtils.isEmpty(omitFields)) {
			throw new IllegalArgumentException("param[omitFields] cannot be a null while omit mode");
		}
		return $copy(destObj, srcObj, true, omitFields);
	}

	@SuppressWarnings("unchecked")
	private static <T1, T2> T1 $copy(T1 destObj, T2 srcObj, boolean isOmit, String... fields) {
		if(destObj == null && srcObj != null && ArrayUtils.isNotEmpty(fields)) {
			destObj = ReflectionUtils.createInstance(srcObj.getClass());
		}
		if(srcObj == null) {
			return null;
		}
		if(destObj == null) {
			// 此种情况，T1和T2必须为相同类型
			return (T1) srcObj;
		}
		if(destObj instanceof Collection) {
			return (T1) _copyCollections((Collection<T1>) destObj, (Collection<T1>) srcObj, isOmit, fields);
		}
		if(destObj.getClass().isArray()) {
			return (T1) _copyArrays((T1[]) destObj, (T1[]) srcObj, isOmit, fields);
		}
		if(ArrayUtils.isEmpty(fields)) {
			_copy(destObj, srcObj);
		} else {
			if(isOmit) {
				_copyWithOmit(destObj, srcObj, fields);
			} else {
				for(String field : fields) {
					_copy(destObj, srcObj, false, field);
				}
			}
		}
		return destObj;
	}

	private static <T1, T2> void _copyWithOmit(T1 destObj, T2 srcObj, String... fields) {
		String[] propertyNames = ReflectionUtils.getFieldNames(srcObj.getClass());
		//先处理第一层需要Merge的Fields
		for(String propertyName : getMergeFields4Omit(propertyNames, fields)) {
			_copy(destObj, srcObj, false, propertyName);
		}
		//再处理下一层需要Merge的Fields
		List<ChildMergeFields> childMergeFieldsList = new ChildMergeFields(srcObj.getClass(), fields).parseChildMergeFields();
		for(ChildMergeFields childMergeFields : childMergeFieldsList) {
			Object srcValue = _getFieldValue(srcObj, childMergeFields._fieldName);
			Object destValue = _getFieldValue(destObj, childMergeFields._fieldName);
			for(String field : childMergeFields._childMergeFields) {
				_copy(destValue, srcValue, false, field);
			}
			$copy(destValue, srcValue, true, childMergeFields._childOmitFields);
		}
	}

	static String[] getMergeFields4Omit(String[] propertyNames, String... fields) {
		List<String> toRemoveFields = new ArrayList<>();
		for(String propertyName : propertyNames) {
			if(containsField(fields, propertyName)) {
				toRemoveFields.add(propertyName);
			}
		}
		return ArrayUtils.removeElements(propertyNames, toRemoveFields.toArray(new String[0]));
	}

	private static boolean containsField(String[] fields, String toFindField) {
		for(String field : fields) {
			if(field.equals(toFindField) || field.startsWith(toFindField + ".")) {
				return true;
			}
		}
		return false;
	}

	static String[] getChildFields(String parentField, String... fields) {
		List<String> childFields = new ArrayList<>();
		for(String field : fields) {
			if(field.startsWith(parentField + ".")) {
				String childField = field.substring(parentField.length() + 1);
				childFields.add(childField);
			}
		}
		return childFields.toArray(new String[0]);
	}

	private static boolean mergeOmit(Object obj, String fieldName) {
		Field srcField = ReflectionUtils.getField(obj, fieldName);
		return mergeOmit(srcField);
	}

	private static boolean mergeOmit(Field srcField) {
		return srcField != null && (Modifier.isTransient(srcField.getModifiers()) || srcField
				.isAnnotationPresent(MergeOmit.class));
	}

	@SuppressWarnings("unchecked")
	private static <T1, T2> T1 _copy(T1 destObj, T2 srcObj, String... fields) {
		if(srcObj == null) {
			return null;
		}
		if(destObj == null) {
			// 此种情况，T1和T2必须为相同类型
			return (T1) srcObj;
		}
		for(Field field : ReflectionUtils.getFields(srcObj.getClass())) {
			String name = field.getName();
			if(ArrayUtils.contains(fields, name) || mergeOmit(destObj, name) || mergeOmit(srcObj, name)) {
				continue;
			}
			try {
				if(ReflectionUtils.hasField(destObj, name)) {
					Object srcValue = _getFieldValue(srcObj, name);
					if(srcValue == null || isSimpleType(srcValue)) {
						_setFieldValue(destObj, name, srcValue);
					} else if(srcValue.getClass().isEnum()) {
						_setFieldValue(destObj, name, srcValue);
					} else {
						if(srcValue.getClass().equals(Object.class)) {
							continue;
						}
						Object destValue = _getFieldValue(destObj, name);
						if(destValue == null) {
							Field fieldDest = ReflectionUtils.getField(destObj.getClass(), name);
							destValue = ReflectionUtils.createInstance(fieldDest.getType());
						}
						//做深度字段copy
						destValue = copy(destValue, srcValue);
						_setFieldValue(destObj, name, destValue);
					}
				}
			} catch(Exception e) {
				logger.error("", e);
			}
		}
		return destObj;
	}

	/**
	 * 根据fieldDesc的描述复制值到对象
	 *
	 * @param destObj
	 * @param srcObj
	 * @param fieldDesc - eg. "group.name"
	 */
	private static <T1, T2> T1 _copy(T1 destObj, T2 srcObj, boolean isOmit, String fieldDesc) {
		if(StringUtils.contains(fieldDesc, '$')) {    //copy单个关联Collection对象的多个字段
			int idx = fieldDesc.indexOf('$');
			String propName = fieldDesc.substring(0, idx);
			String[] fields = StringUtils.split(fieldDesc.substring(idx + 1), ',');
			Collection<Object> srcCollValue = _getFieldValue(srcObj, propName);
			Collection<Object> destCollValue = _getFieldValue(destObj, propName);
			destCollValue = _copyCollections(destCollValue, srcCollValue, isOmit, fields);
			_setFieldValue(destObj, propName, destCollValue);
		} else if(StringUtils.contains(fieldDesc, '.')) {    //copy单个关联普通对象的多个字段
			int idx = fieldDesc.indexOf('.');
			String propName1 = fieldDesc.substring(0, idx);
			String propName2 = fieldDesc.substring(idx + 1);
			Field srcField = ReflectionUtils.getField(srcObj, propName1);
			if(srcField != null) {
				if(mergeOmit(srcField)) {
					return _getFieldValue(destObj, propName1);
				}
				try {
					Object srcValue = _getFieldValue(srcObj, propName1);
					if(srcValue != null) {
						Object destValue = _getFieldValue(destObj, propName1);
						if(destValue == null) {
							PropertyDescriptor descriptorDest = PropertyUtils.getPropertyDescriptor(destObj, propName1);
							destValue = ReflectionUtils.createInstance(descriptorDest.getPropertyType());
						}
						destValue = $copy(destValue, srcValue, isOmit, propName2);
						_setFieldValue(destObj, propName1, destValue);
					}
				} catch(Exception e) {
					logger.error("", e);
					throw new ReflectException(e);
				}
			}
		} else {    //copy单个字段
			if(isOmit) {
				return _getFieldValue(destObj, fieldDesc);
			}
			Object srcValue = _getFieldValue(srcObj, fieldDesc);
			if(srcValue == null || isSimpleType(srcValue)) {
				_setFieldValue(destObj, fieldDesc, srcValue);
			} else {
				Object destValue = _getFieldValue(destObj, fieldDesc);
				//作深度字段copy
				destValue = copy(destValue, srcValue);
				_setFieldValue(destObj, fieldDesc, destValue);
			}
		}
		return destObj;
	}

	@SuppressWarnings("unchecked")
	private static <T> T[] _copyArrays(T[] destObj, T[] srcObj, boolean isOmit, String... fields) {
		Collection<T> result = $copy(Arrays.asList(destObj), Arrays.asList(srcObj), isOmit, fields);
		if(CollectionUtils.isEmpty(result)) {
			return (T[]) result.toArray();
		}
		return toArray(result, (Class<T>) result.iterator().next().getClass());
	}

	private static <T, C extends Collection<T>> C _copyCollections(C destColl, C srcColl, boolean isOmit, String... fields) {
		if(ArrayUtils.isNotEmpty(fields)) {
			if(destColl == null && srcColl != null) {
				destColl = ReflectionUtils.createInstance(srcColl.getClass());
			}
		}
		if(destColl == null || CollectionUtils.isEmpty(srcColl)) {
			return srcColl;
		}
		if(destColl.isEmpty()) {
			for(T srcItem : srcColl) {
				T destItem = $copy(null, srcItem, isOmit, fields);
				destColl.add(destItem);
			}
			return destColl;
		}
		for(T srcItem : srcColl) {
			for(T toItem : destColl) {
				if(srcItem != null && srcItem.equals(toItem)) {
					T result = $copy(toItem, srcItem, isOmit, fields);
					srcItem = result;
				}
			}
		}
		return srcColl;
	}


	private static <T> T _getFieldValue(Object o, String name) {
		return ReflectionUtils.getFieldValue(o, name);
	}

	private static void _setFieldValue(Object bean, String name, Object value) {
		ReflectionUtils.setFieldValue(bean, name, value);
	}
}

/**
 * 辅助类，用于mergeWithOmitFields，标识需要merge和omit的下级fields
 *
 * @author yangylsky
 */
class ChildMergeFields {
	Class<?> objClass;

	String[] omitFields;

	String _fieldName;

	String[] _childMergeFields;

	String[] _childOmitFields;

	ChildMergeFields() {
	}

	ChildMergeFields(Class<?> objClass, String[] omitFields) {
		this.omitFields = omitFields;
		this.objClass = objClass;
	}

	List<ChildMergeFields> parseChildMergeFields() {
		List<ChildMergeFields> list = new ArrayList<>();
		for(String fieldName : parseChildFields()) {
			ChildMergeFields childMergeFields = new ChildMergeFields();
			childMergeFields._fieldName = fieldName;
			Field field = ReflectionUtils.getField(objClass, fieldName);
			String[] propertyNames = ReflectionUtils.getFieldNames(field.getType());
			String[] fields = PojoUtil.getChildFields(fieldName, omitFields);
			childMergeFields._childMergeFields = PojoUtil.getMergeFields4Omit(propertyNames, fields);
			childMergeFields._childOmitFields = ArrayUtils.removeElements(fields, childMergeFields._childMergeFields);
			list.add(childMergeFields);
		}
		return list;
	}

	Set<String> parseChildFields() {
		Set<String> fields = new HashSet<>();
		for(String field : omitFields) {
			if(field.contains(".")) {
				fields.add(field.substring(0, StringUtils.indexOf(field, '.')));
			}
		}
		return fields;
	}
}
